<?php
    /**
     * Created by PhpStorm.
     * Date: 2019/11/1-11:33
     * Author: LuckyHhy
     * 微信企业打款到个人，微信退款，微信扫码支付
     */


    namespace service;

    const SSLCERT_PATH = 'wxpay/wxcert/apiclient_cert.pem'; //本地证书地址
    const SSLKEY_PATH ='wxpay/wxcert/apiclient_key.pem'; //本地证书地址

    class WxPayService
    {

        protected  $config;


        public function __construct(array $config){

            //微信支付商户号 PartnerID 通过微信支付商户资料审核后邮件发送
            if(empty($config["Mchid"])){
               die("商户号不能为空");
            }
            //APPID 通过微信支付商户资料审核后邮件发送
            if(empty($config['AppID'])){
                die("商户账号appid不能为空");
            }
            //https://pay.weixin.qq.com 帐户设置-安全设置-API安全-API密钥-设置API密钥
            if(empty($config['key'])){
                die("key不能为空");
            }

            $this->config=$config;
        }


        /**
         * @param $openid
         * @param $order_id
         * @param $user_name
         * @param $amount
         * @param $desc
         * @return array
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: PayMoney
         * @describe:企业付款   (给指定用户openid打款,转到用用户微信的零钱包里,用于用户申请提现)
         */
        public function PayMoney($openid,$order_id,$user_name,$amount,$desc){
            $params = [
                'mch_appid'=> $this->config['AppID'],//,, //商户账号appid
                'mchid'=>$this->config['Mchid'],  //商户号
                'nonce_str'=>$this->createNonceStr(),  //随机字符串
                'openid'=>$openid,//$openid,//"omMIK43hZfZgChb4vvEDMxrRLJ0c",//个人收款账号(用户的openid),
                'partner_trade_no'=>$order_id,//$order_id //'pu'.mt_rand(10000,99999),  //商户订单号，需保持唯一性(只能是字母或者数字，不能包含有其他字符)
                'check_name'=>'NO_CHECK',  //NO_CHECK：不校验真实姓名       FORCE_CHECK：强校验真实姓名
                're_user_name'=>$user_name,  // 收款用户真实姓名。'check_name'='FORCE_CHECK' 必填
                'amount'=>intval($amount * 100),  // 企业付款金额，单位为元  (不能低于3毛钱)
                'desc'=>$desc,  //企业付款备注，必填
                'spbill_create_ip'=>"",  //该IP同在商户平台设置的IP白名单中的IP没有关联，该IP可传用户端或者服务端的IP。'127.0.0.1'
            ];
            //发起打款式的请求
            $api_url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
            //请求接口
            $arr=$this->unifiedorder($api_url,$params);
            $resArr=array();
            if($arr['result_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS'){
                $resArr["code"] = 1;
            }else{
                $resArr["code"] = 0;
            }
            $resArr["callback_result"] = json_encode($arr,JSON_UNESCAPED_UNICODE);
            return $resArr;
        }



        /**
         * @param $orderInfo
         * @return array
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: WxBackMoney
         * @describe:微信订单号退款
         */
        public  function WxBackMoney($orderInfo){
            $params = [
                'appid'=> $this->config['AppID'],//微信分配的小程序ID
                'mch_id'=>$this->config['Mchid'],  //微信支付分配的商户号
                'nonce_str'=>$this->createNonceStr(),  //随机字符串
                'out_refund_no'=>$orderInfo["order_code"],//商户退款单号
                'total_fee'=>intval($orderInfo["money"]*100),//订单总金额 单位为元
                'refund_fee'=>intval($orderInfo["money"]*100),//退款金额  单位为元
                'out_trade_no'=>$orderInfo["order_code"],// 商户订单号，需保持唯一性(只能是字母或者数字，不能包含有其他字符)
            ];
            //退款请求地址
            $api_url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
            //请求接口
            $arr=$this->unifiedorder($api_url,$params);
            $resArr=array();
            if($arr['result_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS'){
                $resArr["code"] = 0;
            }else{
                $resArr["code"] = 1;
            }
            $resArr["callback_result"] = $arr;
            return $resArr;
        }



        /**
         *  使用例子
        $mchid = "";          //微信支付商户号 PartnerID 通过微信支付商户资料审核后邮件发送
        $appid = "";  //公众号APPID 通过微信支付商户资料审核后邮件发送
        $apiKey = "";   //https://pay.weixin.qq.com 帐户设置-安全设置-API安全-API密钥-设置API密钥
        $wxPay = new WxpayService($mchid,$appid,$apiKey);//实例化微信扫码类
        $outTradeNo = time().$themeEnlist['enlist_card'].mt_rand(10,99);//商品订单号
        $notifyUrl = SystemConfig::getValue('site_url').Url::build('index/Themeenlist/wapay_success_notify');     //付款成功后的回调地址(不要有问号)
        $payTime = time();      //付款时间
        $payAmount=1;
        $arr = $wxPay->createJsBizPackage($payAmount,$outTradeNo,$orderName,$notifyUrl,$payTime);//创建成功会微信返回的数据
        $url = 'http://pan.baidu.com/share/qrcode?w=104&h=100&url='.$arr['code_url'];//生成二维码(连接失效可以自己生产二维码)
         */
        /**
         * @param $totalFee   收款总费用 单位元
         * @param $outTradeNo  唯一的订单号
         * @param $orderName   订单名称
         * @param $notifyUrl   支付结果通知url 不要有问号
         * @param $timestamp
         * @return array
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: createJsBizPackage
         * @describe: 发起订单,扫码支付
         */
        public function createJsBizPackage($totalFee, $outTradeNo, $orderName, $notifyUrl, $timestamp)
        {
            //$orderName = iconv('GBK','UTF-8',$orderName);
            $unified = array(
                'appid' => $this->config['AppID'],
                'attach' => 'pay',             //商家数据包，原样返回，如果填写中文，请注意转换为utf-8
                'body' => $orderName,
                'mch_id' =>  $this->config['Mchid'],
                'nonce_str' =>$this->createNonceStr(),
                'notify_url' => $notifyUrl,
                'out_trade_no' => $outTradeNo,
                'spbill_create_ip' => '127.0.0.1',
                'total_fee' => intval($totalFee * 100),       //单位 转为分
                'trade_type' => 'NATIVE',
            );
            $unified['sign'] = $this->getSigns($unified,  $this->config['key']);
            $responseXml = $this->curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder', $this->ArrToXml($unified));
            $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
            if ($unifiedOrder === false) {
                die('parse xml error');
            }
            if ($unifiedOrder->return_code != 'SUCCESS') {
                die($unifiedOrder->return_msg);
            }
            if ($unifiedOrder->result_code != 'SUCCESS') {
                die($unifiedOrder->err_code);
            }
            $codeUrl = (array)($unifiedOrder->code_url);
            if(!$codeUrl[0]) exit('get code_url error');
            $arr = array(
                "appId" => $this->config['AppID'],
                "timeStamp" => $timestamp,
                "nonceStr" => $this->createNonceStr(),
                "package" => "prepay_id=" . $unifiedOrder->prepay_id,
                "signType" => 'MD5',
                "code_url" => $codeUrl[0],
            );
            $arr['paySign'] = $this->getSigns($arr, $this->config['key']);
            return $arr;
        }


        /**
         * @param string $refundNo 商户退款单号
         * @param string $wxOrderNo 微信订单号
         * @param string $orderNo 商户订单号
         * @param string $refundId 微信退款单号
         * @return bool
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: doRefundQuery
         * @describe:退款查询
         */
        public function doRefundQuery($refundNo='', $wxOrderNo='',$orderNo='',$refundId='')
        {
            $unified = array(
                'appid' => $this->config['AppID'],
                'mch_id' => $this->config['Mchid'],
                'nonce_str' => $this->createNonceStr(),
                'sign_type' => 'MD5',           //签名类型 支持HMAC-SHA256和MD5，默认为MD5
                'transaction_id'=>$wxOrderNo,               //微信订单号
                'out_trade_no'=>$orderNo,        //商户订单号
                'out_refund_no'=>$refundNo,        //商户退款单号
                'refund_id'=>$refundId,     //微信退款单号
            );
            $unified['sign'] = $this->getSign($unified, $this->config['key']);
            $responseXml = $this->curlPost('https://api.mch.weixin.qq.com/pay/refundquery', $this->ArrToXml($unified));
            @file_put_contents('2.txt',$responseXml);
            $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
            if ($unifiedOrder === false) {
                die('parse xml error');
            }
            if ($unifiedOrder->return_code != 'SUCCESS') {
                die($unifiedOrder->return_msg);
            }
            if ($unifiedOrder->result_code != 'SUCCESS') {
                die($unifiedOrder->err_code);
            }
            return true;
        }



        /**
         * @param   //金额
         * @param $out_trade_no  //订单号
         * @param $order_name  //订单标题
         * @param $auth_code  //用户付款码（商户使用设备扫码用户的付款条码读取到的条码数字，或 打开微信-》我-》钱包-》收付款 点击可查看付款码数字）
         * @return array
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: PayCardPackage
         * @describe: 微信刷码支付
         */
        public function PayCardPackage($total_fee,$out_trade_no,$order_name,$auth_code)
        {
            //$order_name = iconv('GBK','UTF-8',$order_name);
            $unified = array(
                'appid' => $this->config['AppID'],
                'attach' => 'pay',             //商家数据包，原样返回，如果填写中文，请注意转换为utf-8
                'body' => $order_name,
                'mch_id' => $this->config['Mchid'],
                'nonce_str' => $this->createNonceStr(),
                'out_trade_no' => $out_trade_no,
                'spbill_create_ip' => '127.0.0.1',
                'total_fee' => intval($total_fee * 100),       //单位 转为分
                'auth_code'=>$auth_code,     //收款码,
                'device_info'=>'dedemao001',        //终端设备号(商户自定义，如门店编号)
                // 'limit_pay'=>'no_credit'            //指定支付方式  no_credit--指定不能使用信用卡支付
            );
            $unified['sign'] = $this->getSign($unified, $this->config['key']);
            $responseXml = $this->curlPost('https://api.mch.weixin.qq.com/pay/micropay', $this->ArrToXml($unified));
            $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
            if ($unifiedOrder === false) {
                die('parse xml error');
            }
            if ($unifiedOrder->return_code != 'SUCCESS') {
                die('支付失败：错误码：'.$unifiedOrder->err_code.'。错误码说明：https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1#7');
            }
            if ($unifiedOrder->result_code != 'SUCCESS') {
                die('支付失败：错误码：'.$unifiedOrder->err_code.'。错误码说明：https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1#7');
            }
            return (array)$unifiedOrder;
        }


        /**
         * @param $wapUrl  //WAP网站URL地址
         * @param $wapName   //WAP 网站名
         * @param $orderName //订单标题
         * @param $notifyUrl //付款成功后的回调地址(不要有问号)
         * @param $outTradeNo  //订单号
         * @param $totalFee  //金额
         * @return \SimpleXMLElement
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: PayH5Package
         * @describe: 微信H5 支付
         */
        public function PayH5Package($wapUrl,$wapName,$orderName,$notifyUrl,$outTradeNo,$totalFee)
        {
            $scene_info = array(
                'h5_info' =>array(
                    'type'=>'Wap',
                    'wap_url'=>$wapUrl,
                    'wap_name'=>$wapName,
                )
            );
            $unified = array(
                'appid' =>$this->config['appid'],
                'attach' => 'pay',             //商家数据包，原样返回，如果填写中文，请注意转换为utf-8
                'body' => $orderName,
                'mch_id' => $this->config['mch_id'],
                'nonce_str' => $this->createNonceStr(),
                'notify_url' => $notifyUrl,
                'out_trade_no' => $outTradeNo,
                'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
                'total_fee' => intval($totalFee * 100),       //单位 转为元
                'trade_type' => 'MWEB',
                'scene_info'=>json_encode($scene_info)
            );
            $unified['sign'] = $this->getSign($unified, $this->config['key']);
            $responseXml = $this->curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder', $this->ArrToXml($unified));
            $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
            if ($unifiedOrder->return_code != 'SUCCESS') {
                die($unifiedOrder->return_msg);
            }
            if($unifiedOrder->mweb_url){
                return $unifiedOrder->mweb_url;
            }
            exit('error');
        }






        /**
         * @return \SimpleXMLElement
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: notify
         * @describe:回调通知
         */
        public function notify()
        {
            $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
            $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
            if ($postObj === false) {
                die('parse xml error');
            }
            if ($postObj->return_code != 'SUCCESS') {
                die($postObj->return_msg);
            }
            if ($postObj->result_code != 'SUCCESS') {
                die($postObj->err_code);
            }
            $arr = (array)$postObj;
            unset($arr['sign']);
            if ($this->getSigns($arr, $this->config['key']) == $postObj->sign) {
                echo '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
                return $postObj;
            }
        }



        /**
         * 获取签名
         */
        private  function getSigns($params, $key)
        {
            ksort($params, SORT_STRING);
            $unSignParaString = $this->formatQueryParaMap($params, false);
            $signStr = strtoupper(md5($unSignParaString . "&key=" . $key));
            return $signStr;
        }



        /**
         * @param $paraMap
         * @param bool $urlEncode
         * @return bool|string
         * @author: hhygyl <hhygyl520@qq.com>
         * @name: formatQueryParaMap
         * @describe:格式化
         */
        private  function formatQueryParaMap($paraMap, $urlEncode = false)
        {
            $buff = "";
            ksort($paraMap);
            foreach ($paraMap as $k => $v) {
                if (null != $v && "null" != $v) {
                    if ($urlEncode) {
                        $v = urlencode($v);
                    }
                    $buff .= $k . "=" . $v . "&";
                }
            }
            $reqPar = '';
            if (strlen($buff) > 0) {
                $reqPar = substr($buff, 0, strlen($buff) - 1);
            }
            return $reqPar;
        }



        /**
         * @param int $length
         * @return string
         * @author: hhygyl <hhygyl520@qq.com>
         * @name: createNonceStr
         * @describe:生成随机字符串
         */
        private  function createNonceStr($length = 16)
        {
            $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
            $str = '';
            for ($i = 0; $i < $length; $i++) {
                $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
            }
            return $str;
        }

        /**
         * @param $api_url
         * @param $params
         * @return mixed|string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: unifiedorder
         * @describe:  //文档对接地址: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4
         */

        private  function unifiedorder($api_url,$params){
            //获取到带签名的数组
            $params = $this->setSign($params);
            //数组转xml
            $xml = $this->ArrToXml($params);
            //发送数据到统一下单API地址
            $data = $this->curlPost($api_url,$xml);
            $arr = $this->XmlToArr($data);
            return $arr;
        }

        /**
         * @param $arr
         * @return string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: getSign
         * @describe: 获取签名
         */
        private  function getSign($arr){
            //去除数组的空值
            array_filter($arr);
            if(isset($arr['sign'])){
                unset($arr['sign']);
            }
            //排序
            ksort($arr);
            //组装字符
            $str = $this->arrToUrl($arr) . '&key=' . $this->config['key'];
            //使用md5 加密 转换成大写
            return strtoupper(md5($str));
        }


        /**
         * @param $arr
         * @return mixed
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: setSign
         * @describe:获取带签名的数组
         */
        private  function setSign($arr){
            $arr['sign'] = $this->getSign($arr);
            return $arr;
        }


        /**
         * @param $arr
         * @return string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: arrToUrl
         * @describe:数组转URL字符串 不带key
         */
        private  function arrToUrl($arr){
            return urldecode(http_build_query($arr));
        }


        /**
         * @return false|string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: getPost
         * @describe:
         */
        protected function getPost(){
            return @file_get_contents('php://input');
        }



        /**
         * @param $xml
         * @return mixed|string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: XmlToArr
         * @describe:Xml 文件转数组
         */
        private  function XmlToArr($xml)
        {
            if($xml == '') return '';
            libxml_disable_entity_loader(true);
            $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
            return $arr;
        }

        /**
         * @param $arr
         * @return string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: ArrToXml
         * @describe:数组转XML
         */
        private  function ArrToXml($arr)
        {
            if(!is_array($arr) || count($arr) == 0) return '';

            $xml = "<xml>";
            foreach ($arr as $key=>$val)
            {
                if (is_numeric($val)){
                    $xml.="<".$key.">".$val."</".$key.">";
                }else{
                    $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
                }
            }
            $xml.="</xml>";
            return $xml;
        }



        /**
         * @param string $url
         * @param string $postData
         * @param array $options
         * @return bool|string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: curlPost
         * @describe:
         */
        private  function curlPost($url = '', $postData = '', $options = array())
        {
            if (is_array($postData)) {
                $postData = http_build_query($postData);
            }
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
            curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
            if (!empty($options)) {
                curl_setopt_array($ch, $options);
            }
            //https请求 不验证证书和host
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            //第一种方法，cert 与 key 分别属于两个.pem文件
            //默认格式为PEM，可以注释
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT,SSLCERT_PATH);
            //默认格式为PEM，可以注释
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY,SSLKEY_PATH);
            //第二种方式，两个文件合成一个.pem文件
            //        curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/all.pem');
            $data = curl_exec($ch);
            curl_close($ch);
            return $data;
        }



        /**
         * @param string $url
         * @param array $options
         * @return bool|string
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @name: curlGet
         * @describe:get 请求
         */
        private  function curlGet($url = '', $options = array())
        {
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
            if (!empty($options)) {
                curl_setopt_array($ch, $options);
            }
            //https请求 不验证证书和host
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            $data = curl_exec($ch);
            curl_close($ch);
            return $data;
        }




    }